在学习angular2的时候,根据angular2中文网提供的quickstart作为种子项目。在安装完所有包并运行项目的时候,出现了满屏的编译错误:

error TS2300: Duplicate identifier

在这里就聊聊这个错误的原因和如何修复它。为了能更好的明白这个问题,我们先讨论TypeScript和Javascript,以及外部类型定义和@types之间的不同。

介绍

在深入之前,先说一下TypeScript的外部类型定义。
如果你比较熟悉TypeScript和JavaScript之间的区别,可以跳过这一部分。

TypeScript是静态类型语言,是JavaScript的超集。静态类型意思是我们所写的程序要经过一个编译的阶段,在这个阶段,编译器执行类型检查,在一定程度上核实正确性。例如:

// person-human.ts

class Person {
  talk() {
    // ...
  }
}

class Superhero extends Person {
  fly() {
    // ...
  }
}

let bar = new Person();

bar.fly();

如果我们编译上面的代码,TypeScript编译器会报错:

$ tsc person-human.ts
Property 'fly' does not exists on type 'Person'.

如果我们运行相应的JavaScript文件,得到错误:

$ node person-human.js
Uncaught TypeError: bar.fly is not a function

虽然上面的两种脚本都报错,但是tsc是在编译时间报的错,而node是在运行时间报的错。

编译时间的错误是容易被开发者处理的,因为错误是基于开发者的静态代码分析的。意味着,在用户使用代码前,编译器能够通知开发者可能的错误。

另一方面,我们使用JavaScript动态类型,我们不可能找出所有我们犯的错误。为动态类型语言的代码进行静态代码分析是很难的(几乎是不可能的),因此,代码发布到生产环境后,仍然没有找到可能存在的问题也是相当有可能的。

外部类型定义

既然TypeScript是JavaScript的超集,我们想一起使用TypeScript代码和JavaScript代码。例如,我们想在Angular2(使用TypeScript写的框架)中使用JQuery。

方案就是:TypeScript编译成JavaScript代码,编译之后就能跟JavaScript代码进行通信,很不错吧。但是如何在TypeScript代码中使用JQuery?看一下下面的例子:

// jquery-demo.ts
let a = $('.foo');

我们调用了$函数选择器,上面的代码会产生一个下面的编译错误:

$ tsc jquery-demo.ts
Cannot find name '$'.

这是因为TypeScript比JavaScript严格,它不能假设,在我们页面里可能包含了一个JQuery的引用,并依赖此引用。TypeScript需要有一个$的申明

这就是外部类型定义出现的原因,为了声明,我们有一个叫做$的全局函数,我们可以这样做:

// jquery-definition.ts
declare var $: Function;

let a = $('.foo');

我们再运行:

$ tsc jquery-definition.ts

# Will output the file jquery-definition.js

注意:我们仅仅是声明了一个$函数,而没有给$函数定义。如果我们运行jquery-definition.js,我们仍然会得到一个运行错误,因为$没有定义(undefined)。

为什么要用外部类型定义

外部类型定义的主要目的是允许文本编辑器和IDE能够对JavaScript写的包和框架进行静态分析。这是很有益处的,因为我们能够编译错误以及开发时能够智能感知。能够减少在运行时,由于属性名拼写错误或传递错误对象给函数而导致的错误。

管理外部类型定义

比较流行用JavaScript写的包都可以使用外部类型定义,例如JQuery、AngularJS 1.x、React等等。

几年前,DefinitelyTyped创建了一个叫作tsd的CLI(Command-Line Interface)管理器

后来这个工具被弃用并且被更先进的typings替换。Typings允许我们下载,我们正在使用的包的外部类型定义

作为TypeScript 2发布的一部分,微软宣布了一种方法,用npm注册去管理TypeScript外部的类型定义

在tsconfig.json的compilerOptions中,包含一个lib属性。它包含了一个library文件集合,以及library相对应的类型定义。意味着如果你的编译目标是ES5并为浏览器building,那么应该包含es5和dom,如果你要用ES6的特性,那么你应该包含es6,等等。更多关于lib属性参考这里
安装JQuery的外部类型定义

$ npm i @types/jquery --save-dev

注意:使用--save-dev比--save更合适,因为你不会希望你的npm package的用户,去安装你的第三方依赖的类型定义

使用外部类型定义

如何使用提供的类型定义呢?

在我们安装JQuery的外部类型定义的目录下,创建下面的文件:

// jquery-demo.ts

/// <reference path="./node_modules/@types/jquery/index.d.ts"/>
let height = $('.foo').height();

虽然可以运行,但是相当不灵活。如果我们改变了定义文件的路径或是其他使用了定义的文件路径被改变,我们就需要改变path属性的值。tsconfig.json给我们提供了一个灵活的特性:

tsconfig.json

tsconfig.json是TypeScript定义的配置文件,在tsconfig.json中,你能配置TypeScript的编译器。这里有个列子:

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "es6"],
    "typeRoots": [
      "./node_modules/@types"
    ]
  },
  "exclude": [
    "node_modules",
    "dist"
  ],
  "compileOnSave": false
}

为了告诉tsc到哪寻找安装的外部类型定义,你可以使用typeRoots数组,是compilerOptions的属性。

在配置文件中,使用es5作为目标语言,排除node_modules和dist目录。

为了有ES6 APIs的类型定,需要安装@types/core-js:

$ npm i @types/core-js

最后我们的目录结构为:

.
├── jquery-demo.js
├── jquery-demo.ts
├── tsconfig.json
└── node_modules
    └── @types
        ├── jquery
        │   ├── package.json
        │   │── types-metadata.json
        │   └── index.d.ts
        └── core-js
            ├── package.json
            │── types-metadata.json
            └── index.d.ts

由于tsconfig.json,我们能用下面命令编译jquery-demo.ts文件:

$ tsc

结果,得到很多错误

./../.npm-packages/lib/node_modules/typescript/lib/lib.es2015.core.d.ts(17,14): error TS2300: Duplicate identifier 'PropertyKey'.
node_modules/@types/core-js/index.d.ts(21,14): error TS2300: Duplicate identifier 'PropertyKey'.
node_modules/@types/core-js/index.d.ts(85,5): error TS2687: All declarations of 'name' must have identical modifiers.
node_modules/@types/core-js/index.d.ts(145,5): error TS2403: Subsequent variable declarations must have the same type.  Variable '[Symbol.unscopables]' must be of type '{ copyWithin: boolean; entries: boolean; fill: boolean; find: boolean; findIndex: boolean; keys: ...', but here has type 'any'.
node_modules/@types/core-js/index.d.ts(262,5): error TS2687: All declarations of 'flags' must have identical modifiers.
...

这由相同类型定义的多版本导致的。在tsconfig.json中,lib属性包含了es6。同时在node_modules/@types中也有core-js模块。为了修复这个问题,两个选项:

  • 在compilerOptions的lib属性中,改es6为es5.这种方法TypeScript不能包含ES6类型定义

  • 从node_modules中删除core-js模块。这种方式TypeScript仅能使用内部的ES6类型定义

因为使用TypeScript自带的ES6类型定义比第三方的更靠谱,那就删除node_modules/@types/core-js吧

$ rm -rf node_modules/@types/core-js

如果我们再运行tsc, 会得到编译过的jquery-demo.js文件

即使我们从jquery-demo.ts删除<reference/>标签,仍然是运行正常的。tsc的行为是:“Take all the files and all the type definitions from the current directory and all of its subdirectories, except the ones declared in the exclude array”,意味着我们也能使用tsconfig.json的exclude属性管理外部类型定义

tsconfig.json还有files属性,如果设置了它,tsc将值考虑files中包含的文件。

没有命名空间的类型定义

你可能疑惑的为什么compilerOptions的lib和core-js有相同的类型定义?TypeScript外部类型定义是不能用命名空间。为什么不呢?如果在项目中使用JQuery,我们不能包含两个不同的外部类型定义集合,因为只有一个单独的JQuery全局变量

参考链接:
http://blog.mgechev.com/2016/...
http://stackoverflow.com/ques...


stone
33 声望0 粉丝

在这个无奈的社会,我还在为我的美好生活而奋斗,如果我无暇顾及陌生的你,我想你怪不了我